package com.test.learnkotlin.test

/*
Kotlin let, run, also, apply, with
https://www.journaldev.com/19467/kotlin-let-run-also-apply-with


Kotlin系列之let、with、run、apply、also函数的使用
https://blog.csdn.net/u013064109/article/details/78786646

巧用Kotlin：内置函数let、also、with、run、apply大大提高你的开发效率！
https://cloud.tencent.com/developer/article/1591238

also    https://play.kotlinlang.org/byExample/06_scope_functions/05_also
它也像apply一样工作:它执行一个给定的块并返回被调用的对象。在块内部，对象被它引用，所以它更容易作为参数传递。这个函数可以方便地嵌入其他操作，比如在调用链中登录。

apply
https://play.kotlinlang.org/byExample/06_scope_functions/04_apply
Apply在对象上执行代码块并返回对象本身。在块内部，对象被this引用。这个函数对于初始化对象很方便。

let
https://play.kotlinlang.org/byExample/06_scope_functions/01_let
The Kotlin standard library function let can be used for scoping and null-checks. When called on an object,
let executes the given block of code and returns the result of its last expression.
 The object is accessible inside the block by the reference it (by default) or a custom name.

 Kotlin标准库函数let可用于范围确定和空检查。当调用对象时，let执行给定的代码块并返回其最后一个表达式的结果。对象可以通过引用(默认情况下)或自定义名称在块中访问。

 run
https://play.kotlinlang.org/byExample/06_scope_functions/02_run

与let类似，run是标准库中的另一个作用域函数。基本上，它做同样的事情:执行一个代码块并返回其结果。
不同的是，在内部运行对象是通过this访问的。当您想要调用对象的方法而不是将其作为参数传递时，这是很有用的。

with    https://play.kotlinlang.org/byExample/06_scope_functions/03_with
With是一个非扩展函数，可以简洁地访问其参数的成员:在引用其成员时，可以省略实例名。

 */
fun main() {

    val testData = TestData("test", 20)
    val result_Let = testData?.let {
        it.name = "let test"
        TestData("new test", 10)
    }

    // let 在函数块内可以通过 it 指代该对象。返回值为函数块的最后一行或指定return表达式。

    println("result_Let = ${result_Let.toString()}")
    //result_Let = TestData(name='new test')

    val result_also = testData.also {
        it.name = "also test"
        TestData("new test also", 10)
    }

    //also函数：返回值 = 传入的对象的本身
    println("result_also = ${result_also.toString()}")
    //result_also = TestData(name='also test')

    getTestData().also {// also 在块内部，对象被它引用，所以它更容易作为参数传递。这个函数可以方便地嵌入其他操作，比如在调用链中登录。
        it.token?.let {
            getUserInfo(it)
        }
    }


    //with是kotlin标准库里的内联（inline）函数

    //with函数和前面的几个函数使用方式略有不同，因为它不是以扩展的形式存在的。它是将某对象作为函数的参数，在函数块内可以通过 this 指代该对象。返回值为函数块的最后一行或指定return表达式。
    val result_with = with(testData) {//调用同一个对象的多个方法 / 属性时，可以省去对象名重复，直接调用方法名 / 属性即可
        println("with 函数里 打印 testData name = $name  , add(6) = ${add(6)}")
        "这是 with 函数里的倒数第二行"
        "这是 with 函数里的最后一行"

    }

    println("result_with = ${result_with.toString()}")
    //with 函数里 打印 testData name = also test  , add(6) = 11
    //result_with = 这是 with 函数里的最后一行


    //内联扩展函数之run
    //run函数实际上可以说是let和with两个函数的结合体，run函数只接收一个lambda函数为参数，以闭包形式返回，返回值为最后一行的值或者指定的return的表达式。
    val result_run_testData = testData.run {
        name = "testData run"
        200
        "内联扩展函数之run"
    }

    println("result_run_testData = $result_run_testData")
    //result_run_testData = 内联扩展函数之run

    //run是kotlin标准库里的内联（inline）函数
    val result_run = run {//返回值 = 函数块的最后一行 / return表达式
        2000
        "这是直接 run 的 倒数第二行"
        "这是直接 run "

    }

    println("result_run = $result_run")
    //result_run = 这是直接 run


    //内联扩展函数之apply
    //apply在对象上执行代码块并返回对象本身。在块内部，对象被this引用。这个函数对于初始化对象很方便。
    val subjects = Subjects()

    //apply函数的返回的是传入对象的本身
    val result_apply = subjects.apply {
        name = "kotlin"
        auth = "android"
        date = "2016"

        "这是 apply 函数 最后一行"
    }
    println("result_apply = ${result_apply.toString()}")
    //result_apply = Subjects(name=kotlin, auth=android, date=2016)

    /*

        also函数：返回的是传入的对象的本身
                 在函数块内可以通过 it 指代该对象

        apply函数：返回的是传入对象的本身
                   在函数块内可以通过 this 指代该对象

        with函数：返回值为函数块的最后一行或指定return表达式。
                  with是kotlin标准库里的内联（inline）函数
                  在函数块内可以通过 this 指代该对象

        let函数：返回值为函数块的最后一行或指定return表达式。
                let 在函数块内可以通过 it 指代该对象。

        run函数：返回值为最后一行的值或者指定的return的表达式。
                在函数块内可以通过 this 指代该对象


         https://blog.csdn.net/u013064109/article/details/78786646
         run函数实际上可以说是let和with两个函数的结合体，run函数只接收一个lambda函数为参数，以闭包形式返回，返回值为最后一行的值或者指定的return的表达式。

         适用于let,with函数任何场景。因为run函数是let,with两个函数结合体，准确来说它弥补了let函数在函数体内必须使用it参数替代对象，
         在run函数中可以像with函数一样可以省略，直接访问实例的公有属性和方法，另一方面它弥补了with函数传入对象判空问题，在run函数中可以像let函数一样做判空处理


     */
    val testWhen1 = testWhen1()
    val testWhen2 = testWhen2()
    val testWhen3 = testWhen3()
    println("whenResult1 = $whenResult1")
    //whenResult1 = 数字 20
    // 如果 testWhen1 执行了else return 语句 打印的 whenResult1 = 默认值
    println("testWhen1 = $testWhen1 , testWhen2 = $testWhen2 , testWhen3 = $testWhen3")
    //testWhen1 whenResult = 数字 20
    //testWhen3 whenResult = huawei 设备
    //testWhen1 = 数字 20 , testWhen2 = 未知 , testWhen3 = kotlin.Unit
    //注意 testWhen3里的打印语句没有执行 因为 else 里直接 return "未知"


    println("----------------------- 揭秘 Kotlin 中的 == 和 === ---------------------")
    testDengdengHeDengDengDeng()


}

var whenResult1:String= "默认值"
fun testWhen1(): String {
    val anum = 20
    whenResult1 = when (anum) {
        1 -> "数字1"
        20 -> "数字 20"
        else -> return "未知"
    }
    println("testWhen1 whenResult = $whenResult1")

    return whenResult1
}

fun testWhen2(): String {//有返回值 可以 在分支语句里直接 return
    val isHuaWei = false
    val isXiaoMi = false
    val whenResult = when {
        isHuaWei -> "huawei 设备"
        isXiaoMi -> "xiaomi 设备"
        else -> return "未知"
    }
    println("testWhen2 whenResult = $whenResult")
    return whenResult
}

fun testWhen3() {//没有返回值
    val isHuaWei = true
    val isXiaoMi = false
    val whenResult = when {
        isHuaWei -> "huawei 设备"
        isXiaoMi -> "xiaomi 设备"
        else ->  "未知"
    }
    println("testWhen3 whenResult = $whenResult")
}


/*
Kotlin 构造函数、继承
https://www.jianshu.com/p/fc54d2241ba7

注意主构造函数的参数如果使用了var或val修饰符，就相当于在类中声明了对应名称的属性。

 */
class TestData(var name: String, age: Int) {
    constructor(name: String, age: Int, address: String) : this(name, age)

    var token: String? = null

    fun getNmae() = name
    override fun toString(): String {
        return "TestData(name='$name')"
    }

    fun add(num: Int): Int = num + 5


}

class Subjects {
    var name: String? = null
    var auth: String? = null
    var date: String? = null
    override fun toString(): String {
        return "Subjects(name=$name, auth=$auth, date=$date)"
    }


}

fun getTestData(): TestData = TestData("getTestData", 200)

fun getUserInfo(token: String): String = ""

data class TestDengDeng(val name: String,val age:Int)


/*
    揭秘 Kotlin 中的 == 和 ===
    https://mp.weixin.qq.com/s/sYj_-wqENr9Jaw1p8iP4Jg

 */
fun testDengdengHeDengDengDeng() {
    /*
        Kotlin 中的操作符 == 和 === 及 equals
        Kotlin 提供了两种方式用于对象的比较。

        比较对象的结构是否相等（ == 或者 equals ）

        Kotlin 中的操作符 == 等价于 equals 用于比较对象的结构是否相等, 很多情况下使用的是 ==，因为对于浮点类型 Float 和 Double，其实现方法 equals 不遵循 IEEE 754 浮点运算标准。

        比较对象的引用是否相等 ( === )

        Kotlin 中的操作符 === 用于比较对象的引用是否指向同一个对象，运行时如果是基本数据类型 === 等价于 ==。

        我们知道了基本概念之后，接下来一起来看一下这些操作符（ == 和 === 及 equals ），在以下场景中的使用。

        基本数据类型
        包装类
        普通类
        数据类
        基本数据类型

        运行时，对于基本数据类型 === 等价于 == 比较的是值（即对象的结构是否相等），如果比较基本数据类型时使用 ===，编译器就会给出一个警告，不建议使用。

     */
    val a1 = -0
    val a2 = 0
    println("a1 == a2 ${a1 == a2}") //true
    println("a1 === a2 ${a1 === a2}") //true
    println("a1 equals a2 ${a1.equals(a2)}") //true

    val b1 = 100
    val b2 = 100
    println("b1 == b2 ${b1 == b2}") //true
    println("b1 === b2 ${b1 === b2}") //true
    println("b1 equals b2 ${b1.equals(b2)}") //true

    //但是 equals 比较特殊, 对于浮点类型 Float 和 Double 却有不同的表现，代码如下所示。
    val a3 = -0f
    val a4 = 0f
    println("a3 == a4 ${a3 == a4}")       // true
    println("a3.equals(a4) ${a3.equals(a4)}")  // false
    println("a3 === a4 ${a3 === a4}")      // true
    //正如你所看到的 a3.equals(a4) 结果为 false，那么为什么会这样呢，一起来查看反编译后的 Java 代码都做了什么。Tools → Kotlin → Show Kotlin Bytecode 。


    val a5 = Integer(10)
    val a6 = Integer(10)
    println(a5 == a6)       // true
    println(a5.equals(a6))  // true
    println(a5 === a6)      // false
    //因为包装类重写了 equals 方法，所以使用操作符 == 和 equals 比较的是对象的结构是否相等，所以结果为 true。
    // 而操作符 === 比较的是对象的引用，是否指向同一个对象，因为是不同的对象，所以结果为 false。

    println("---------------------------------------- === 普通的类 == -------------------------------------------------------")


    //普通的类
    //普通的类其实就是我们自己新建的类，并没有重写 equals 方法，一起来看一下这三种操作符的运行结果。
    val testData1= TestData("t1",20)
    val testData2= TestData("t1",20)
    println("testData1 == testData2 ${testData1 == testData2}")       // false
    println("testData1.equals(testData2) ${testData1.equals(testData2)}")  // false
    println("testData1 === testData2 ${testData1 === testData2}")      // false

    println("testData1.name == testData2.name ${testData1.name == testData2.name}")         // true
    println("testData1.name.equals(testData2.name) ${testData1.name.equals(testData2.name)}")    // true
    println("testData1.name === testData2.name ${testData1.name === testData2.name}")        // true

    println("---------------------------------------- === 数据类 == -------------------------------------------------------")

    //数据类
    //最后我们在来看一下这三种操作符在数据类中的表现。

    val testDengDeng1= TestDengDeng("t1",20)
    val testDengDeng2= TestDengDeng("t1",20)
    println("testDengDeng1 == testDengDeng2 ${testDengDeng1 == testDengDeng2}")       // true
    println("testDengDeng1.equals(testDengDeng2) ${testDengDeng1.equals(testDengDeng2)}")  // true
    println("testDengDeng1 === testDengDeng2 ${testDengDeng1 === testDengDeng2}")      // false

    println("testDengDeng1.name == testDengDeng2.name ${testDengDeng1.name == testDengDeng2.name}")         // true
    println("testDengDeng1.name.equals(testDengDeng2.name) ${testDengDeng1.name.equals(testDengDeng2.name)}")    // true
    println("testDengDeng1.name === testDengDeng2.name ${testDengDeng1.name === testDengDeng2.name}")        // true

    //因为编译器会根据数据类中的参数，自动生成 equals 、 hashCode 、 toString 等等方法，查看编译后的代码


}